﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Internal.KuiHuaBaoDian;
using KuiHuaBaoDian.Services;

namespace KuiHuaBaoDian {

    /// <summary>
    /// 类型映射表
    /// </summary>
    public interface ITypeMap {

        /// <summary>
        /// 注册
        /// </summary>
        /// <typeparam name="TInterface">接口类型</typeparam>
        /// <typeparam name="TImplemented">实现类型</typeparam>
        /// <param name="logEnabled">是否启用注册时发生的日志</param>
        void Register<TInterface, TImplemented>(bool logEnabled) where TImplemented : class, TInterface, new();

        /// <summary>
        /// 注册
        /// </summary>
        /// <param name="interfaceType">接口类型</param>
        /// <param name="implementedType">实现类型</param>
        /// <param name="logEnabled">是否启用注册时发生的日志</param>
        void Register([NotNull] Type interfaceType, [NotNull] Type implementedType, bool logEnabled = true);

        /// <summary>
        /// 实例化
        /// </summary>
        /// <typeparam name="TInterface">接口类型</typeparam>
        /// <returns>实现了<typeparamref name="TInterface"/>接口的类型的实例</returns>
        [return: NotNull]
        TInterface Instantiate<TInterface>();

        /// <summary>
        /// 实例化
        /// </summary>
        /// <param name="interfaceType">接口类型</param>
        /// <param name="genericArguments">泛型类型数组</param>
        /// <returns>实现了<paramref name="interfaceType"/>接口的类型的实例</returns>
        [return: NotNull]
        object Instantiate([NotNull] Type interfaceType, Type[] genericArguments = null);

        /// <summary>
        /// 获取实现类型
        /// </summary>
        /// <typeparam name="TInterface">接口类型</typeparam>
        /// <returns>实现了<typeparamref name="TInterface"/>接口的类型</returns>
        [return: NotNull]
        Type Fetch<TInterface>();

        /// <summary>
        /// 获取实现类型
        /// </summary>
        /// <param name="interfaceType">接口类型</param>
        /// <returns>实现了<paramref name="interfaceType"/>接口的类型</returns>
        [return: NotNull]
        Type Fetch([NotNull] Type interfaceType);

        /// <summary>
        /// 释放
        /// </summary>
        /// <param name="poolObject"></param>
        void Recycle(IPoolObject poolObject);
    }

    [DebuggerStepThrough]
    [DebuggerNonUserCode]
    internal sealed class KHBD_TypeMap : ITypeMap {

        private readonly IDictionary<Type, Type> m_ImplementedDictionary = new Dictionary<Type, Type>();
        private readonly IDictionary<Type, Type> m_InterfaceDictionary = new Dictionary<Type, Type>();
        private readonly IDictionary<Type, ConstructorInfo> m_ConstructorInfoDictionary = new Dictionary<Type, ConstructorInfo>();

        private readonly IDictionary<Type, Stack> m_ObjectPoolStackDictionary = new Dictionary<Type, Stack>();
        private readonly HashSet<object> m_PoolObjectSet = new();

        public void Register<TInterface, TImplemented>(bool logEnabled = true) where TImplemented : class, TInterface, new() => Register(typeof(TInterface), typeof(TImplemented), logEnabled);

        public void Register(Type interfaceType, Type implementedType, bool logEnabled = true) {
            UnityEngine.Assertions.Assert.IsNotNull(interfaceType);
            UnityEngine.Assertions.Assert.IsNotNull(implementedType);
            UnityEngine.Assertions.Assert.IsTrue(interfaceType.IsInterface, $"{interfaceType.FullName} 不是接口");
            UnityEngine.Assertions.Assert.IsFalse(implementedType.IsInterface, $"{implementedType.FullName} 不能是接口");

            if (interfaceType.IsGenericType) {
                // TODO: 泛型的提前比对
            } else {
                UnityEngine.Assertions.Assert.IsTrue(interfaceType.IsAssignableFrom(implementedType), $"{implementedType.FullName} 没有实现 {interfaceType.FullName}");
                if (!typeof(UnityEngine.Component).IsAssignableFrom(implementedType)) {
                    UnityEngine.Assertions.Assert.IsNotNull(implementedType.GetConstructor(Type.EmptyTypes), $"{implementedType.FullName} 没有公开的无参构造");
                }
            }
            Add(interfaceType, implementedType, logEnabled);
        }

        private void Add(Type interfaceType, Type implementedType, bool logEnabled = true) {
            if (m_ImplementedDictionary.TryGetValue(interfaceType, out var lastImplementedType)) {
                if (KHBD.Context.IsApplicationPlaying && logEnabled) {
                    UnityEngine.Debug.Log($"当前 [实现({implementedType.AssemblyQualifiedName})] 覆盖对 [接口({interfaceType.AssemblyQualifiedName})] 的上一个实现 [实现({lastImplementedType.AssemblyQualifiedName})]");
                }
            }
            m_ImplementedDictionary[interfaceType] = implementedType;
            m_InterfaceDictionary[implementedType] = interfaceType;
            if (implementedType.GetInterface(typeof(IPoolObject).FullName) != null && !m_ObjectPoolStackDictionary.ContainsKey(implementedType)) {
                m_ObjectPoolStackDictionary.Add(implementedType, new Stack());
            }
        }

        public TInterface Instantiate<TInterface>() {
            if (!KHBD.Context.IsApplicationPlaying) {
            }
            var interfaceType = typeof(TInterface);
            UnityEngine.Assertions.Assert.IsNotNull(interfaceType);
            UnityEngine.Assertions.Assert.IsTrue(interfaceType.IsInterface, $"{interfaceType.FullName} 不是接口");

            if (!TryGetCache(interfaceType, out var instance)) {
                if (!m_ConstructorInfoDictionary.TryGetValue(interfaceType, out var constructor)) {
                    var genericArguments = interfaceType.GetGenericArguments();
                    if (genericArguments != null) {
                        foreach (var genericArgument in genericArguments) {
                            if (genericArgument.IsGenericParameter) {
                                genericArguments = null;
                                break;
                            }
                        }
                    }
                    TInterface result;
                    var @new = DoInstantiate(typeof(TInterface), genericArguments);
#if UNITY_EDITOR
                    try {
                        result = (TInterface)@new;
                    } catch (System.Exception e) {
                        UnityEngine.Debug.LogError($"无法将 {@new?.GetType().FullName} 转换为 {typeof(TInterface).FullName} \n{@new}\n{e}");
                        throw;
                    }
#else
                    result = (TInterface)@new;
#endif
                    return result;
                }
                return (TInterface)Construct(constructor);
            }
            return (TInterface)instance;
        }

        public object Instantiate(Type interfaceType, Type[] genericArguments = null) {
            UnityEngine.Assertions.Assert.IsNotNull(interfaceType);
            UnityEngine.Assertions.Assert.IsTrue(interfaceType.IsInterface, $"{interfaceType.FullName} 不是接口");

            if (!TryGetCache(interfaceType, out var instance)) {
                instance = DoInstantiate(interfaceType, genericArguments);
            }
            return instance;
        }

        private bool TryGetCache(Type interfaceType, out object instance) {
            if (m_ImplementedDictionary.TryGetValue(interfaceType, out var implementedType) && m_ObjectPoolStackDictionary.TryGetValue(implementedType, out var stack)) {
                if (stack.Count > 0) {
                    instance = stack.Pop();
                    (instance as IPoolObject).Allocate();
                    return true;
                }
            }
            instance = null;
            return false;
        }

        private object DoInstantiate(Type interfaceType, Type[] genericArguments = null) {
            if (!m_ConstructorInfoDictionary.TryGetValue(interfaceType, out var constructor)) {
                var implementedType = Fetch(interfaceType);
                UnityEngine.Assertions.Assert.IsFalse(typeof(UnityEngine.Component).IsAssignableFrom(implementedType), $"不允许实例化继承于{typeof(UnityEngine.Component).FullName}的组件，请{nameof(Fetch)}出实例类型后，使用Unity相关API添加组件");
                if (implementedType.ContainsGenericParameters) {
                    genericArguments ??= interfaceType.GetGenericArguments();
                    UnityEngine.Assertions.Assert.IsNotNull(genericArguments, $"实例化的对应的实现存在泛型参数，请使用 {nameof(Instantiate)}({nameof(Type)}[]) 实例化泛型类型, {nameof(interfaceType)}={interfaceType.FullName}, {nameof(implementedType)}={implementedType.FullName}");
                    implementedType = implementedType.MakeGenericType(genericArguments);
                }
                constructor = implementedType.GetConstructor(Type.EmptyTypes);
                m_ConstructorInfoDictionary.Add(interfaceType, constructor);
            }
            return Construct(constructor);
        }

        private object Construct(ConstructorInfo constructor) {
            UnityEngine.Assertions.Assert.IsNotNull(constructor);

            var instance = constructor.Invoke(null);
            m_PoolObjectSet.Add(instance);
            if (instance is IService service) {
                (KHBD.Runtime as IContextInternal).AddService(service);
            }
            if (instance is IPoolObject poolObject) {
                poolObject.Allocate();
            }
            return instance;
        }

        public Type Fetch<TInterface>() => Fetch(typeof(TInterface));
        public Type Fetch(Type interfaceType) {
            UnityEngine.Assertions.Assert.IsNotNull(interfaceType);
            UnityEngine.Assertions.Assert.IsTrue(interfaceType.IsInterface, $"{interfaceType.FullName} 不是接口");

            if (!m_ImplementedDictionary.TryGetValue(interfaceType, out var implementedType)) {
                if (interfaceType.IsGenericType) {
                    interfaceType = interfaceType.GetGenericTypeDefinition();
                }
                m_ImplementedDictionary.TryGetValue(interfaceType, out implementedType);
            }

            UnityEngine.Assertions.Assert.IsNotNull(implementedType, $"没有找到 {interfaceType.FullName} 的实现");
            return implementedType;
        }

        public void Recycle(IPoolObject poolObject) {
            if (!m_PoolObjectSet.Contains(poolObject)) {
                return;
            }
            var implementedType = poolObject.GetType();
            if (m_ObjectPoolStackDictionary.TryGetValue(implementedType, out var stack)) {
                stack.Push(poolObject);
            }
        }
    }
}
